﻿/** \file
* \par Subversion URL:
* \$URL: http://planar-rendering.googlecode.com/svn/trunk/frontend/src/Generator.cs $
* \version \$Rev: 78 $
* \date    \$Date: 2009-04-20 09:47:39 +0000 (Mon, 20 Apr 2009) $
*/

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;

namespace Frontend
{
	/// <summary>
	/// A class to generate fractal data. Encapsulates every setting describing
	/// the fractal as well as marshalling code to call the unmanaged DLL.
	/// </summary>
	class Generator
	{
		/////////////////////////////////////////////////////////////////////////////
		// Native functions /////////////////////////////////////////////////////////

		/// <summary>
		/// Unmanaged win32 call to get a function pointer from a loaded DLL.
		/// </summary>
		/// <param name="hmodule">A handle to the DLL.</param>
		/// <param name="lpProcName">The symbol name of the function.</param>
		/// <returns>A pointer to the function.</returns>
		[DllImport("kernel32", CharSet = CharSet.Ansi)]
		public extern static IntPtr GetProcAddress(IntPtr hmodule, string lpProcName);

		/// <summary>
		/// A delegate to a fractal function in the unmanaged DLL.
		/// </summary>
		/// <param name="context">A pointer to the generation context structure.</param>
		/// <param name="funcs">An ordered list of functions to apply during iteration.</param>
		/// <param name="parms">An ordered list of the parameters that the applied functions require.</param>
		/// <param name="prow">A pointer to a buffer of bools to receive the fractal membership data.</param>
		[UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
		delegate void delFunction(
			[In]  [MarshalAs(UnmanagedType.LPStruct)] Context context,
			[In]  [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U4)] int[] funcs,
			[In]  [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.R8)] double[] parms,
			[Out] [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1)] byte[] prow);

		/// <summary>The delegate instance for the unmanaged fractal function.</summary>
		delFunction func;

		/////////////////////////////////////////////////////////////////////////////
		// Properties ///////////////////////////////////////////////////////////////

		/// <summary>
		/// An enumeration describing the different complex spaces available for fractal generation.
		/// </summary>
		public enum SpaceType : uint
		{
			/// <summary>Quaternionic space.</summary>
			quat,
			/// <summary>Co-quaternionic space.</summary>
			coquat,
			/// <summary>Hyperbolic-quaternionic space.</summary>
			hypquat,
			/// <summary>Hypercomplex commutative space.</summary>
			hypcplx
		};

		/// <summary>The instance of the space enumeration.</summary>
		public SpaceType space;

		/// <summary>The number of threads in one generation session.</summary>
		public const int nthreads = 2;

		/// <summary>The ranges, sorted by resolution.</summary>
		public List<Range> varranges;
		/// <summary>The spatial parameter ranges.</summary>
		public List<Range> spatialranges;
		/// <summary>All ranges associated with this function, including spatials and non-spatials.</summary>
		public List<Range> ranges = new List<Range>();
		/// <summary>The list of functions to apply during iteration.</summary>
		public List<Function> funcs = new List<Function>();

		/// <summary>The context structure instance.</summary>
		public Context con = new Context();

		/// <summary>The enumerated values associated with the iteration functions.</summary>
		int[] funcenums = new int[0];
		/// <summary>The parameter values associated with the ranges.</summary>
		double[][] rangevals = new double[nthreads][];
		/// <summary>The row of fractal membership booleans.</summary>
		public byte[][] row = new byte[nthreads][];

		/// <summary>The target to which this generation session will output.</summary>
		Target target;

		/// <summary>The number of threads currently running.</summary>
		int tcount;

		/////////////////////////////////////////////////////////////////////////////
		// Methods //////////////////////////////////////////////////////////////////

		/// <summary>
		/// The constructor. Allocates the range value array.
		/// </summary>
		public Generator()
		{
			for (int t = 0; t < nthreads; t++)
				rangevals[t] = new double[0];
		}

		/// <summary>
		/// Adds a new range to this generation session.
		/// </summary>
		/// <param name="func">The function that will own the range.</param>
		/// <returns>The new range object.</returns>
		public Range AddRange(Function func)
		{
			for (int t = 0; t < nthreads; t++)
				Array.Resize(ref rangevals[t], rangevals[t].Length + 1);
			foreach (Range range in ranges)
				range.vals = rangevals;

			Range newrange = new Range(rangevals, ranges.Count);
			ranges.Add(newrange);
			func.ranges.Add(newrange);
			return newrange;
		}

		/// <summary>
		/// Adds a new function to this generation session.
		/// </summary>
		/// <param name="fact">The function factory to use to generate the function.</param>
		/// <returns>The new function object.</returns>
		public Function AddFunction(AbstractFunctionFactory fact)
		{
			Function newFunc = fact.Instantiate(this);
			if (funcs.Count > 0)
			{
				funcs.Insert(funcs.Count - 1, newFunc);
				Array.Resize(ref funcenums, funcs.Count);
				funcenums[funcenums.Length - 1] = funcenums[funcenums.Length - 2];
				funcenums[funcenums.Length - 2] = fact.enumIndex;
			}
			else
			{
				funcs.Add(newFunc);
				Array.Resize(ref funcenums, 1);
				funcenums[0] = fact.enumIndex;
			}

			return newFunc;
		}

		/// <summary>
		/// Remove a function from this generation session.
		/// </summary>
		/// <param name="index">The index of the function to remove.</param>
		public void RemoveFunction(int index)
		{
			funcs.RemoveAt(index);
			for (int i = index; i < funcenums.Length - 1; i++)
				funcenums[i] = funcenums[i + 1];
			Array.Resize(ref funcenums, funcs.Count);
		}

		/// <summary>
		/// Begin the generation session. Initializes various generation variables,
		/// the unmanaged function delegate, and the target.
		/// </summary>
		/// <param name="targetInit">The target to use for this session.</param>
		/// <returns>The resolution of the outermost parametric range.</returns>
		public int Begin(Target targetInit)
		{
			target = targetInit;

			List<Range> nonspatials = new List<Range>();
			spatialranges = new List<Range>();
			foreach (Range range in ranges)
			{
				if (range.res > 1)
				{
					range.SetDelta();
					range.InsertResSorted(range.spatial ? spatialranges : nonspatials);
				}
				else
					range.ResetSingleVals();
			}
			varranges = nonspatials;
			varranges.AddRange(spatialranges);

			// Initialize the context
			con.pindex = varranges[varranges.Count - 1].valsindex;
			con.InitParam(ranges[con.pindex]);

			// Allocate the rows
			for (int t = 0; t < nthreads; t++)
				row[t] = new byte[con.pres];
			
			// Initialize the delegate
			string name = "func_" + space.ToString();
			IntPtr fptr = GetProcAddress(Program.hlib, name);
			if (fptr == IntPtr.Zero)
				throw new Exception("Could not load DLL symbol " + name);
			func = (delFunction)Marshal.GetDelegateForFunctionPointer(fptr, typeof(delFunction));

			target.Begin();

			return varranges[0].res;
		}

		/// <summary>
		/// Start all of the fractal generation threads.
		/// </summary>
		public void Run()
		{
			tcount = 0;
			for (int t = 0; t < nthreads; t++)
			{
				Thread thread = new Thread(SessionThread);
				thread.Start(t);
			}
		}

		/// <summary>
		/// The generator thread entry procedure.
		/// </summary>
		/// <param name="param">The thread number.</param>
		void SessionThread(object param)
		{
			int threadno = (int)param;

			tcount++;
			
			Range range = varranges[0];
			int top = range.res*(threadno+1)/nthreads;
			for (range.ResetFirstRangeVal(threadno); range.resindex[threadno] < top; range.IncrementVal(threadno))
			{
				RunRecurse(threadno, 1);
				Program.IncrementProgress();
			}

			tcount--;
			if (tcount == 0)
			{
				target.End();
				Program.EndGenerate();
			}
		}

		/// <summary>
		/// The recursive function to iterate through a parametric range and call the
		/// next inner parameter iteration.
		/// </summary>
		/// <param name="threadno">The thread number.</param>
		/// <param name="vindex">The index of the varying parameter.</param>
		void RunRecurse(int threadno, int vindex)
		{
			if (vindex == varranges.Count-1)
			{
				func(con, funcenums, rangevals[threadno], row[threadno]);
				target.Write(threadno);
				return;
			}

			Range range = varranges[vindex];
			for (range.ResetRangeVal(threadno); range.resindex[threadno] < range.res; range.IncrementVal(threadno))
				RunRecurse(threadno, vindex + 1);
		}
	}
}
